package online.inote.naruto.utils.time;

import online.inote.naruto.utils.Assert;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.time.*;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @description 日期时间转换器
 * @author gaopengsui@creditease.cn
 * @date 2021/09/10 16:28
 */
public class Converter {

  public static Date toDate(LocalDateTime dateTime) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    return Date.from(toInstant(dateTime));
  }

  public static Date toDate(LocalDate date) {
    Assert.notNull(date, "date cannot be null");
    return Date.from(date.atStartOfDay(Zone.DEFAULT).toInstant());
  }

  public static Date toDate(LocalTime time) {
    Assert.notNull(time, "date cannot be null");
    return Date.from(toInstant(LocalDate.now().atTime(time)));
  }

  public static Date toDate(Instant instant) {
    return Date.from(instant);
  }

  public static Date toDate(long epochMilli) {
    return new Date(epochMilli);
  }

  public static Date toDate(ZonedDateTime zonedDateTime) {
    Assert.notNull(zonedDateTime, "zonedDateTime cannot be null");
    return Date.from(zonedDateTime.toInstant());
  }

  public static Date toDate(TemporalAccessor temporal) {
    return toDate(toZonedDateTime(temporal));
  }

  public static LocalDateTime toLocalDateTime(Date date) {
    Assert.notNull(date, "date cannot be null");
    return Instant.ofEpochMilli(date.getTime()).atZone(Zone.DEFAULT).toLocalDateTime();
  }

  public static LocalDateTime toLocalDateTime(Timestamp timestamp) {
    Assert.notNull(timestamp, "timestamp cannot be null");
    return timestamp.toLocalDateTime();
  }

  public static LocalDateTime toLocalDateTime(LocalDate localDate) {
    Assert.notNull(localDate, "localDate cannot be null");
    return localDate.atStartOfDay();
  }

  public static LocalDateTime toLocalDateTime(LocalTime localTime) {
    Assert.notNull(localTime, "localTime cannot be null");
    return LocalDate.now().atTime(localTime);
  }

  public static LocalDateTime toLocalDateTime(Instant instant) {
    return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
  }

  public static LocalDateTime toLocalDateTime(long epochMilli) {
    Assert.notNull(epochMilli, "epochMilli cannot be null");
    return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Zone.DEFAULT);
  }

  public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime) {
    Assert.notNull(zonedDateTime, "zonedDateTime cannot be null");
    return zonedDateTime.toLocalDateTime();
  }

  public static LocalDateTime toLocalDateTime(TemporalAccessor temporal) {
    Assert.notNull(temporal, "temporal cannot be null");
    return toLocalDateTime(toZonedDateTime(temporal));
  }

  public static LocalDate toLocalDate(Date date) {
    Assert.notNull(date, "date cannot be null");
    return toLocalDateTime(date).toLocalDate();
  }

  public static LocalDate toLocalDate(LocalDateTime dateTime) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    return dateTime.toLocalDate();
  }

  public static LocalDate toLocalDate(Instant instant) {
    return toLocalDateTime(instant).toLocalDate();
  }

  public static LocalDate toLocalDate(long epochMilli) {
    return toLocalDateTime(epochMilli).toLocalDate();
  }

  public static LocalDate toLocalDate(TemporalAccessor temporal) {
    return toLocalDateTime(temporal).toLocalDate();
  }

  public static LocalDate toLocalDate(ZonedDateTime zonedDateTime) {
    return toLocalDateTime(zonedDateTime).toLocalDate();
  }

  public static LocalDate toLocalDate(YearMonth yearMonth, int dayOfMonth) {
    Assert.notNull(yearMonth, "yearMonth cannot be null");
    return yearMonth.atDay(dayOfMonth);
  }

  public static ZonedDateTime toZonedDateTime(Date date) {
    return toZonedDateTime(date, Zone.DEFAULT);
  }

  public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId) {
    Assert.notNull(date, "date cannot be null");
    Assert.notNull(zoneId, "zoneId cannot be null");
    return Instant.ofEpochMilli(date.getTime()).atZone(zoneId);
  }

  public static ZonedDateTime toZonedDateTime(LocalDateTime dateTime) {
    return toZonedDateTime(dateTime, Zone.DEFAULT);
  }

  public static ZonedDateTime toZonedDateTime(LocalDateTime dateTime, ZoneId zoneId) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    Assert.notNull(zoneId, "zoneId cannot be null");
    return dateTime.atZone(zoneId);
  }

  public static ZonedDateTime toZonedDateTime(LocalDate date, ZoneId zoneId) {
    Assert.notNull(date, "date cannot be null");
    Assert.notNull(zoneId, "zoneId cannot be null");
    return date.atStartOfDay(zoneId);
  }

  public static ZonedDateTime toZonedDateTime(LocalDate date) {
    return toZonedDateTime(date, Zone.DEFAULT);
  }

  public static ZonedDateTime toZonedDateTime(TemporalAccessor temporal) {
    Assert.notNull(temporal, "temporal cannot be null");

    if (temporal instanceof Instant) {
      return toZonedDateTime((Instant) temporal);
    } else if (temporal instanceof LocalDate) {
      return toZonedDateTime((LocalDate) temporal);
    } else if (temporal instanceof LocalDateTime) {
      return toZonedDateTime((LocalDateTime) temporal);
    } else if (temporal instanceof LocalTime) {
      return toZonedDateTime((LocalTime) temporal);
    } else if (temporal instanceof ZonedDateTime) {
      return (ZonedDateTime) temporal;
    } else if (temporal instanceof YearMonth) {
      return toZonedDateTime((YearMonth) temporal);
    } else if (temporal instanceof OffsetDateTime) {
      return ((OffsetDateTime) temporal).toZonedDateTime();
    } else if (temporal instanceof OffsetTime) {
      return ((OffsetTime) temporal).atDate(LocalDate.now()).toZonedDateTime();
    } else {
      return ZonedDateTime.from(temporal);
    }
  }

  public static LocalTime toLocalTime(Date date) {
    Assert.notNull(date, "date cannot be null");
    return toZonedDateTime(date).toLocalTime();
  }

  public static LocalTime toLocalTime(LocalDateTime dateTime) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    return dateTime.toLocalTime();
  }

  public static LocalTime toLocalTime(Instant instant) {
    Assert.notNull(instant, "instant cannot be null");
    return toZonedDateTime(instant).toLocalTime();
  }

  public static LocalTime toLocalTime(TemporalAccessor temporal) {
    Assert.notNull(temporal, "temporal cannot be null");
    return toZonedDateTime(temporal).toLocalTime();
  }

  public static LocalTime toLocalTime(ZonedDateTime zonedDateTime) {
    Assert.notNull(zonedDateTime, "zonedDateTime cannot be null");
    return zonedDateTime.toLocalTime();
  }

  public static Instant toInstant(Date date) {
    Assert.notNull(date, "date cannot be null");
    return date.toInstant();
  }

  public static Instant toInstant(LocalDateTime dateTime) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    return dateTime.atZone(Zone.DEFAULT).toInstant();
  }

  public static Instant toInstant(LocalDate localDate) {
    return toZonedDateTime(localDate).toInstant();
  }

  public static Instant toInstant(LocalTime localTime) {
    return toZonedDateTime(localTime).toInstant();
  }

  public static Instant toInstant(long epochMilli) {
    return Instant.ofEpochMilli(epochMilli);
  }

  public static Instant toInstant(TemporalAccessor temporal) {
    return toInstant(toZonedDateTime(temporal));
  }

  public static Instant toInstant(ZonedDateTime zonedDateTime) {
    Assert.notNull(zonedDateTime, "zonedDateTime cannot be null");
    return zonedDateTime.toInstant();
  }

  public static Instant toInstant(Timestamp timestamp) {
    Assert.notNull(timestamp, "timestamp cannot be null");
    return timestamp.toInstant();
  }

  public static long toEpochMilli(Date date) {
    Assert.notNull(date, "date cannot be null");
    return date.getTime();
  }

  public static long toEpochMilli(LocalDateTime dateTime) {
    Assert.notNull(dateTime, "dateTime cannot be null");
    return toInstant(dateTime).toEpochMilli();
  }

  public static long toEpochMilli(LocalDate date) {
    Assert.notNull(date, "date cannot be null");
    return toInstant(date).toEpochMilli();
  }

  public static long toEpochMilli(Instant instant) {
    Assert.notNull(instant, "date cannot be null");
    return instant.toEpochMilli();
  }

  public static long toEpochMilli(ZonedDateTime zonedDateTime) {
    Assert.notNull(zonedDateTime, "zonedDateTime cannot be null");
    return zonedDateTime.toInstant().toEpochMilli();
  }

  public static long toEpochMilli(Timestamp timestamp) {
    Assert.notNull(timestamp, "date cannot be null");
    return timestamp.getTime();
  }

  public static long toEpochMilli(TemporalAccessor temporal) {
    Assert.notNull(temporal, "temporal cannot be null");
    return toEpochMilli(toZonedDateTime(temporal));
  }

  public static long convert(long sourceDuration, TimeUnit sourceUnit, TimeUnit targetUnit) {
    Assert.notNull(sourceUnit, "sourceUnit cannot be null");
    Assert.notNull(targetUnit, "targetUnit cannot be null");
    return targetUnit.convert(sourceDuration, sourceUnit);
  }

  /**
   * 单位精确转换
   *
   * @param sourceDuration 源数量
   * @param sourceUnit 原单位
   * @param targetUnit 目标单位
   * @param scope 精度
   * @param roundingMode 四舍五入模式
   * @return 结果
   */
  public static BigDecimal convertPrecise(
      long sourceDuration,
      TimeUnit sourceUnit,
      TimeUnit targetUnit,
      int scope,
      RoundingMode roundingMode) {
    Assert.notNull(sourceUnit, "sourceUnit cannot be null");
    Assert.notNull(targetUnit, "targetUnit cannot be null");
    Assert.notNull(roundingMode, "roundingMode cannot be null");
    return BigDecimal.valueOf(sourceDuration)
        .multiply(BigDecimal.valueOf(sourceUnit.toNanos(1)))
        .divide(BigDecimal.valueOf(targetUnit.toNanos(1)), scope, roundingMode);
  }
}
